傳統的 Javascript 使用 function 加上 protyotype-based 來繼承建立元件
但是這樣的機制對程式開發者習慣 Object-oriented 的感覺很尷尬
ECMAScript 2015 與 ECMAScript6 中允許開發者使用 object-oriented class-based approach
最基本的 class-based 範例
class Greeter{
  greeting: string;
  constructor(messaage: string){
    this.greeting = message;
  }
  greet(){
    return "Hello, " + this.greeting;
  }
}
let greeter = new Greeter("world");
這樣的程式對 C# 或是 Java 的開發者應該會比較親切
宣告了一個新的 class Greeter
這個 class 中有一個屬性是 greeting constructor 和 greet
看到有一個關鍵字 this. 之後可以呼叫這個 class 的屬性
在 TypeScript 中可以直接使用 object-oriented patterns
當然也可以建立一個 class 做繼承的動作,
範例
class Animnal {
  name: string;
  constructor(theName: string) {
    this.name = theName;
  }
  move(distanceInMeters: number = 0) {
    console.log(`${this.name} moved ${distanceInMeters}`);
  }
}
class Snake extends Animnal {
  constructor(name: string) {
    super(name);
  }
  move(distanceInMeters = 5) {
    console.log("Slithering...");
    super.move(distanceInMeters);
  }
}
class Horse extends Animnal {
  constructor(name: string) {
    super(name);
  }
  move(distanceInMeters = 45) {
    console.log("Galloping...");
    super.move(distanceInMeters);
  }
}
let sam = new Snake("Sammy the Python");
let tom: Animnal = new Horse("Tommy the Palomino");
sam.move();
tom.move(34);
上述範例中以 extends 這個關鍵字來建立一個子類
Horse 和 Snake 是繼承在 class Animal 之下的子類
在子類中的 constructor 必須使用 super() 這將會執行父類的 constructor
這個範例也示範了如何覆寫父類的 Function 在 Snake 和 Horse 都有建立一個 move 的 Function 來覆寫過 Animal 的 move 執行結果後如下
Slithering...
Sammy the Python moved 5m.
Galloping...
Tommy the Palomino moved 34m.
在我們的範例中可以自由地宣告屬性
但在其他語言(C#) 需要使用 public 這個關鍵字來規範屬性是不是可以被瀏覽
但是在 TypeScript 中 public 是預設值
但是你也可以使用 public 來宣告屬性
class Animal{
  public name: string;
  public constructor(theName: string){
    this.name = theName;
  }
  public move(distanceInMeters: number){
    console.log(`${this.name} moved ${distanceInMeters}m.`);
  }
}
當某個屬性使用 private 來宣告
他不能來宣告,他不能被直接呼叫
class Animal{
  private name: string;
  constructor(theName: string) { this.name = theName; }
}
new Animal("Cat").name; // Error: 'name' is private
TypeScript 是一個結構型態系統
我們比較兩種不同的類別
不論他們是如何產生的
只要他們的所有屬性沒有衝突
我們就可以稱這兩個類別是相容的
class Animal {
    private name: string;
    constructor(theName: string) { this.name = theName; }
}
class Rhino extends Animal {
    constructor() { super("Rhino"); }
}
class Employee {
    private name: string;
    constructor(theName: string) { this.name = theName; }
}
let animal = new Animal("Goat");
let rhino = new Rhino();
let employee = new Employee("Bob");
animal = rhino;
animal = employee; // Error: 'Animal' and 'Employee' are not compatible
然而當兩個類別在比較的時候如果擁有 private 和 protected 屬性
除非他們所這個 private 和 protected 繼承的是同一個父類別才會是兼容的
否則在形態上兩個都會是不同的
class Animal{
  private name: string;
  constructor(theName: string){
    thie.name = theName;
  }
}
class Rhino extends Animal{
  constructor(){
    super("Rhino");
  }
}
class Employee{
  private name: string;
  constructor(theName: string){
    this.name = theName;
  }
}
let animal = new Animal("Goat");
let rhino = new Rhino();
let employee = Employee("Bob");
animal = rhino;
animal = employee; // Error: 'Animal' and 'Employee' are not compatible
在這個範例中我們有 Animal 和 Rhino 兩個 class
Rhino 是 Animal 的子類別
另外也有一個 Employee 他看起來和 Animal 十分相似
都有一個 private name: string
因為 Rhino 是繼承 Animal 所以 Animal 實體化後可以 assign 給 Rhino 的實體並不衝突
代表他們是相容的,而 Employee 即使有一樣的 private name: string 但是卻無法相容,
因為他們並不是在同一個父類的類別
protected 和 private 很相似,只是當你宣告為 protected
class Person {
  protected name: string;
  constructor(name: string) {
    this.name = name;
  }
}
class Employee extends Person {
  private department: string;
  constructor(name: string, department: string) {
    super(name);
    this.department = department;
  }
  public getElevatorPitch() {
    return `Hello, my name is ${this.name} and I work in ${this.department}.`;
  }
}
let howard = new Employee("Howard", "Sales");
console.log(howard.getElevatorPitch());
// console.log(howard.name); // error
我們沒辦法直接呼叫 name 但是可以透過 Employee instance method 來使用,因為 Employee 繼承自 Person
我們也可以將 constructor 宣告為 protected 這代表這個 class 只能用來繼承,而無法直接產生 instance
class Person {
  protected name: string;
  protected constructor(theName: string) {
    this.name = theName;
  }
}
// Employee can extend Person
class Employee extends Person {
  private department: string;
  constructor(name: string, department: string) {
    super(name);
    this.department = department;
  }
  public getElevatorPitch() {
    return `Hello, my name is ${this.name} and I work in ${this.department}.`;
  }
}
let howard = new Employee("Howard", "Sales");
//let john = new Person("John"); // Error: The 'Person' constructor is protected
你可以宣告某些參數或變數是 readonly 使用 readonly 這個關鍵字來宣告\
但是必須在初始化或是在 constructor 的時候進行宣告
class Octopus{
  readonly name: string;
  readonly numberOfLegs: number = 8;
  constructor(theName: string){
    this.name = theName;
  }
}
let dad = new Octopus('Man with the 8 strong legs');
//dad.name = "Man with the 3-piece suit"; // error! name is readonly.
TypeScript 支援 getters/setters 去對 Object 中的屬性進行取值或是修改
class Employee{
  fulllName: string;
}
let employee = new Employee();
employee.fullName = 'Bob Smith';
if(employee.fullName){
  console.log(employee.fullName);
}
我們希望使用者是有足夠的安全性,所以使用 private 宣告 fullName 然後允許使用 set 來對 fullName 來做修改
let passcode = 'secret passcode';
class Employee{
  private _fulllName: string;
  get fullName(): string{
    return this._fullName;
  }
  set fullName(newName: string){
    if(passcode && passcode === 'secret passcode'){
      this._fulllName = newName;
    }else{
      console.log("Error: Unauthorized update of employee!");
    }
  }
}
let employee = new Employee();
employee.fullName = "Bob Smith";
if (employee.fullName) {
    console.log(employee.fullName);
}
command line
  $ tsc -t ES5 ./Accessors.ts
有兩點需要注意
ECMAScript 5 以上才可以使用 Accessors
get 而沒有設定 set 代表這個屬性是 readonly
在這個部分我們討論的是實體的屬性
也是靜態屬性 實體的屬性 也是靜態屬性 這個屬性只能在 class 中取得
而無法被繼承
class Grid {
  static origin = { x: 0, y: 0 };
  calculateDistanceFromOrigin(point: { x: number; y: number }) {
    let xDist = point.x - Grid.origin.x;
    let yDist = point.y - Grid.origin.y;
    return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
  }
  constructor(public scale: number) {}
}
let grid1 = new Grid(1.0); // 1x scale
let grid2 = new Grid(5.0); // 5x scale
console.log(grid1.calculateDistanceFromOrigin({ x: 10, y: 10 }));
console.log(grid2.calculateDistanceFromOrigin({ x: 10, y: 10 }));
抽象類別就像之前的類別一樣 也許不需要實體化
使用 abstract 關鍵字來宣告抽象類別與抽象函式
abstract class Animal{
  abstract makeSound(): void;
  move(): void{
    console.log("roaming the earth..");
  }
}
abstract 中的函式並不會並不會包含在實體
也一定會使用 abstract 關鍵字來做宣告定義
abstract class Department{
  constructor(public name: string){
  }
  printName():void{
    console.log("Department name: " + this.name);
  }
  abstract printMeeting():void;
}
class AccountingDepartment extends Department{
  constructor(){
    super("Accounting and Auditing");
  }
  printMeeting():void{
    console.log("The Accounting Department meets each Monday at 10am.");
  }
  generateReports():void{
    console.log("Generating accounting reports...");
  }
}
let department: Department; // ok to create a reference to an abstract type
// department = new Department(); // error: cannot create an instance of an abstract class
department = new AccountingDepartment(); // ok to create and assign a non-abstract subclass
department.printName();
department.printMeeting();
// department.generateReports(); // error: method doesn't exist on declared abstract type
抽象類別無法直接使用 new 產生物件
若是在抽象類別中並沒有宣告的類別與屬性 其子類別即使寫了也無法使用
在 TypeScript 中宣告一個 class 的時候,其實你已經同時執行了多個宣告
class Greeter {
  greeting: string;
  constructor(message: string){
    this.greeting=  message;
  }
  greet(){
    return "Hello, " + this.greeting;
  }
}
let greeter: Greeter;
greeter = new Greeter("world");
console.log(greeter.greet());
上述範例中當 let greeter: Greeter 我們將會使用 Greeter 類別的 instance 賦予 class Greeter
當我們使用 new 這個關鍵字來實體化的時候
便會執行 constructor 轉譯之後的結果如下
let Greeter = (function () {
    function Greeter(message) {
        this.greeting = message;
    }
    Greeter.prototype.greet = function () {
        return "Hello, " + this.greeting;
    };
    return Greeter;
})();
let greeter;
greeter = new Greeter("world");
console.log(greeter.greet());
在 let Greeter 就會準備指定給 constructor
而看到接下來的 new 關鍵字並且開始執行 constructor 就會取得一個藉由 Gretter 這個函式實體化的一個結果
在修改一下上面的範例
class Greeter {
  static standardGreeting = "Hello, there";
  greeting: string;
  greet() {
    if (this.greeting) {
      return "Hello, " + this.greeting;
    } else {
      return Greeter.standardGreeting;
    }
  }
}
let greeter1: Greeter;
greeter1 = new Greeter();
console.log(greeter1.greet());
let greeterMaker: typeof Greeter = Greeter;
let greeter2: Greeter = new greeterMaker();
console.log(greeter2.greet());
console.log(greeter1.greet());
greeterMaker.standardGreeting = "Hey there!";
console.log(greeter2.greet());
console.log(greeter1.greet());
在這個範例中我們在 Greeter 宣告了一個靜態的屬性 standardGreeting並且給予值 Hello, there
第一步驟跟之前的範例一樣,利用 Greeter 產生了一個物件是 greeter1 然後將他的類別 assign 給 greeterMaker
並且修改了他的 standardGreeting 為 Hey there! 之後再由 greeterMaker 產生一個 greeter2 當它的 greet()
執行的時候產生的字串卻是 Hey there! 而且此時我再次執行 greeter1.greet() 的時候得到的卻也是 Hey there!
也就是當我們 可以利用 這樣的方式統一管理一個靜態屬性也會互相繼承靜態屬性